#include "httpresponse.h"

using namespace std;

const unordered_map<string, string> HttpResponse::SUFFIX_TYPE = {
    { ".html",  "text/html" },
    { ".xml",   "text/xml" },
    { ".xhtml", "application/xhtml+xml" },
    { ".txt",   "text/plain" },
    { ".rtf",   "application/rtf" },
    { ".pdf",   "application/pdf" },
    { ".word",  "application/nsword" },
    { ".png",   "image/png" },
    { ".gif",   "image/gif" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".au",    "audio/basic" },
    { ".mpeg",  "video/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".avi",   "video/x-msvideo" },
    { ".gz",    "application/x-gzip" },
    { ".tar",   "application/x-tar" },
    { ".css",   "text/css "},
    { ".js",    "text/javascript "},
};

const unordered_map<int, string> HttpResponse::CODE_STATUS = {
    { 200, "OK" },
    { 400, "Bad Request" },
    { 403, "Forbidden" },
    { 404, "Not Found" },
};

const unordered_map<int, string> HttpResponse::CODE_PATH = {
    { 400, "/400.html" },
    { 403, "/403.html" },
    { 404, "/404.html" },
};

//客户端发的AXIOS
const unordered_set<string> NOT_RES_PAGE = {
  "/list/get","/login","/upload","/list/mkdir","/list/del","/list/download"
};


HttpResponse::HttpResponse() {
    code_ = -1;
    path_ = srcDir_ = "";
    isKeepAlive_ = false;
    mmFile_ = nullptr; 
    mmFileStat_ = { 0 };
};

HttpResponse::~HttpResponse() {
    UnmapFile();
}

void HttpResponse::Init(const string& srcDir, const char *request_body,string& path, bool isKeepAlive, int code){
  assert(srcDir != "");
  if(mmFile_) { UnmapFile(); }
  code_ = code;
  isKeepAlive_ = isKeepAlive;
  path_ = path;
  srcDir_ = srcDir;
  this->request_body = request_body;
  mmFile_ = nullptr; 
  mmFileStat_ = { 0 };
}

void HttpResponse::MakeResponse(Buffer& buff) {
  if(NOT_RES_PAGE.count(path_) == 0){
    if(stat((srcDir_ + path_).data(), &mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode)) {
      code_ = 404;
    }
    else if(!(mmFileStat_.st_mode & S_IROTH)) {//判断是否其他用户可以访问
      code_ = 403;
    }
    else if(code_ == -1) { 
      code_ = 200; 
    }
  }
  ErrorHtml_();
  AddStateLine_(buff);
  AddHeader_(buff);
  AddContent_(buff);
}

char* HttpResponse::File() {
    return mmFile_;
}

size_t HttpResponse::FileLen() const {
    return mmFileStat_.st_size;
}

//错误码就返回错误页面
void HttpResponse::ErrorHtml_() {
  if(CODE_PATH.count(code_) == 1) {
    path_ = CODE_PATH.find(code_)->second;
    stat((srcDir_ + path_).data(), &mmFileStat_);
  }
}

//响应行
void HttpResponse::AddStateLine_(Buffer& buff) {
  string status;
  if(CODE_STATUS.count(code_) == 1) {
    status = CODE_STATUS.find(code_)->second;
  }else{
    code_ = 400;
    status = CODE_STATUS.find(400)->second;
  }
  buff.Append("HTTP/1.1 " + to_string(code_) + " " + status + "\r\n");
}

//响应头
void HttpResponse::AddHeader_(Buffer& buff) {
  buff.Append("Connection: ");
  if(isKeepAlive_) {
    buff.Append("keep-alive\r\n");
    buff.Append("keep-alive: max=6, timeout=120\r\n");
  } else{
    buff.Append("close\r\n");
  }
  buff.Append("Content-type: " + GetFileType_() + "\r\n");
}

void HttpResponse::AddContent_(Buffer& buff) {
    if(path_ == "/list/get"){
      response_with_file_list(buff);return;
    }
    if(path_ == "/login"){
      deal_with_login(buff);return;
    }
    if(path_ == "/list/mkdir"){
      mkdir_in_file_list(buff);return;
    }
    if(path_ == "/list/del"){
      del_in_file_list(buff);return;
    }
    if(path_ == "/upload"){
      deal_upload(buff);return;
    }
    if(path_ == "/list/download"){
      deal_download(buff);return;
    }

    int srcFd = open((srcDir_ + path_).data(), O_RDONLY);
    if(srcFd < 0) { 
        ErrorContent(buff, "File NotFound!");
        return; 
    }

    /* 将文件映射到内存提高文件的访问速度 
        MAP_PRIVATE 建立一个写入时拷贝的私有映射*/
    LOG_DEBUG("file path %s", (srcDir_ + path_).data());
    int* mmRet = (int*)mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0);
    if(*mmRet == -1) {
        ErrorContent(buff, "File NotFound!");
        return; 
    }
    mmFile_ = (char*)mmRet;//指向内存区域的首地址
    close(srcFd);
    buff.Append("Content-length: " + to_string(mmFileStat_.st_size) + "\r\n\r\n");
}

//释放mmap映射内存
void HttpResponse::UnmapFile() {
    if(mmFile_) {
        munmap(mmFile_, mmFileStat_.st_size);
        mmFile_ = nullptr;
    }
}

//确定响应头的content-type
string HttpResponse::GetFileType_() {
  if(NOT_RES_PAGE.count(path_) == 1){//对前端的axios请求,都返回json
    if(path_ == "/list/download"){
      return "application/octet-stream";
    }
    return "application/json";
  }
  string::size_type idx = path_.find_last_of('.');
  if(idx == string::npos) {//path_中没有.,默认text/plain
    return "text/plain";
  }
  string suffix = path_.substr(idx);//根据path_后缀确定类型
  if(SUFFIX_TYPE.count(suffix) == 1) {
    return SUFFIX_TYPE.find(suffix)->second;
  }
  return "text/plain";
}

void HttpResponse::ErrorContent(Buffer& buff, string message) 
{
    string body;
    string status;
    body += "<html><title>Error</title>";
    body += "<body bgcolor=\"ffffff\">";
    if(CODE_STATUS.count(code_) == 1) {
        status = CODE_STATUS.find(code_)->second;
    } else {
        status = "Bad Request";
    }
    body += to_string(code_) + " : " + status  + "\n";
    body += "<p>" + message + "</p>";
    body += "<hr><em>TinyWebServer</em></body></html>";

    buff.Append("Content-length: " + to_string(body.size()) + "\r\n\r\n");
    buff.Append(body);
}



//用户和当前所在的层级
void HttpResponse::response_with_file_list(Buffer& buff){
  nlohmann::json jsonObject = nlohmann::json::parse(request_body);
  string pt = jsonObject["usr"];
  string lm = jsonObject["cur"];


  MYSQL* sql;
  SqlConnRAII tmp_val(&sql,  SqlConnPool::Instance());
  assert(sql);

  //将SQL语句写入到sql_str数组中
  char sql_str[256];
  snprintf(sql_str, 256, "SELECT * FROM user_files WHERE username='%s' and parent_dir='%s'",
    pt.c_str(),lm.c_str());

  if(mysql_query(sql, sql_str)) {//Zero for success. Nonzero if an error occurred.
    LOG_ERROR("mysql_query() error");
  }
  MYSQL_RES *res = mysql_store_result(sql);
  unsigned int j = mysql_num_fields(res);//结果集的列数

  nlohmann::json res_arr = nlohmann::json::array();
  MYSQL_ROW row;
  while((row = mysql_fetch_row(res)) != NULL){
    nlohmann::json obj = {{"filename",row[2]},{"upload_time",row[3]},{"filesize",atoi(row[4])},
    {"desc",row[5]},{"is_dir",atoi(row[6])}};
    res_arr.push_back(obj);
  }
  nlohmann::json jsob = {{"code",200},{"msg","获取成功"},{
    "data",res_arr
  }};
  string ret = "\r\n" + jsob.dump();
  buff.Append(ret);
  mysql_free_result(res);  
}

void HttpResponse::deal_with_login(Buffer& buff){
  nlohmann::json jsonObject = nlohmann::json::parse(request_body,nullptr,false);
  string pt = jsonObject["username"];
  string lm = jsonObject["password"];

  MYSQL* sql;
  SqlConnRAII tmp_val(&sql,  SqlConnPool::Instance());
  assert(sql);

  //将SQL语句写入到sql_str数组中
  char sql_str[256];
  snprintf(sql_str, 256, "SELECT * FROM user_info WHERE username='%s'",pt.c_str());

  if(mysql_query(sql, sql_str)) {//Zero for success. Nonzero if an error occurred.
    LOG_ERROR("mysql_query() error");
  }
  MYSQL_RES *res = mysql_store_result(sql);
  unsigned int j = mysql_num_fields(res);//结果集的列数

  
  nlohmann::json respon;
  if(MYSQL_ROW row = mysql_fetch_row(res)){//有数据,判断密码
    LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);
    string password(row[2]);
    if(lm == password){
      respon = {{"code",200},{"msg","登陆成功"}};
    }else{
      respon = {{"code",401},{"msg","密码错误"}};
    }
  }else{//没数据,就进行注册
    snprintf(sql_str, 256, "INSERT INTO user_info VALUES(null,'%s','%s',now())",
    pt.c_str(),lm.c_str());
    if(mysql_query(sql, sql_str)){ 
      LOG_ERROR("mysql_query() error");
    }
    //创建用户目录
    //mkdir不能递归的创建多级目录,所以user_data目录必须存在
    char user_home[strlen("user_data/u_")+strlen(pt.c_str())+1] = "user_data/u_";
    strcat(user_home,pt.c_str());
    int ret = mkdir(user_home,0777);
    if(ret == -1){
      perror("mkdir() failed for creating user_home");
    }

    respon = {{"code",200},{"msg","注册成功"}};
  }
  string ret = "\r\n" + respon.dump();//响应头和响应体之间是两个换行(注意)
  buff.Append(ret);
  mysql_free_result(res);
}

void HttpResponse::mkdir_in_file_list(Buffer& buff){
  nlohmann::json jsonObject = nlohmann::json::parse(request_body,nullptr,false);
  string pt = jsonObject["usr"];
  string lm = jsonObject["cur"];
  string dir_name = jsonObject["dir_name"];

  string dst = "user_data/u_"+pt+lm+dir_name;
  int ret = mkdir(dst.c_str(),0777);
  if(ret == -1){
    perror("mkdir() failed");
    nlohmann::json respon;
    respon = {{"code",400},{"msg","创建目录失败"}};
    string ret = "\r\n" + respon.dump();//响应头和响应体之间是两个换行(注意)
    buff.Append(ret);
  }else{
    MYSQL* sql;
    SqlConnRAII tmp_val(&sql,  SqlConnPool::Instance());
    assert(sql);

    //将SQL语句写入到sql_str数组中
    char sql_str[256];
    snprintf(sql_str, 256,"INSERT INTO user_files values(null,'%s','%s',now(),%d,'测试使用',1,'%s')",
    pt.c_str(),dir_name.c_str(),0,lm.c_str());

    if(mysql_query(sql, sql_str)) {//Zero for success. Nonzero if an error occurred.
      LOG_ERROR("mysql_query() error");
    }
    nlohmann::json respon;
    respon = {{"code",200},{"msg","创建目录成功"}};
    string ret = "\r\n" + respon.dump();//响应头和响应体之间是两个换行(注意)
    buff.Append(ret);
  }  
}

void HttpResponse::del_in_file_list(Buffer& buff){}

void HttpResponse::deal_upload(Buffer& buff){
  nlohmann::json respon;
  respon = {{"code",200},{"msg","上传成功"}};
  string ret = "\r\n" + respon.dump();//响应头和响应体之间是两个换行(注意)
  buff.Append(ret);
}

//前端那里使用AXIOS(POST+JSON),前端那里需要使用blob的方式来接收下载
void HttpResponse::deal_download(Buffer& buff){
  nlohmann::json jsonObject = nlohmann::json::parse(request_body,nullptr,false);
  string pt = jsonObject["usr"];
  string lm = jsonObject["file_path"];

  string dst = "user_data/u_"+pt+lm;

  char buf[BUFSIZ];
  int ret = open(dst.c_str(),O_RDONLY);
  if(ret == -1){perror("open() failed");}else{

    struct stat resq; 
    stat(dst.c_str(),&resq);
    char CL[55];
    sprintf(CL,"Content-Length: %d\r\n",resq.st_size);
    buff.Append(CL,strlen(CL)); 
    
    string nm = "Content-Disposition: attachment;filename=";
    buff.Append(nm);
    sprintf(CL,"\"%s\"\r\n\r\n",lm.substr(lm.find_last_of('/')+1).c_str());
    buff.Append(CL,strlen(CL));

    int len;
    while((len = read(ret,buf,BUFSIZ))){
      buff.Append(buf,len);
    }
    close(ret);
  }
}